from itertools import izip, islice
import simplejson

class ParamType(object):
    pass
    
class String(ParamType):
    pass
    
class Boolean(ParamType):
    pass

class Integer(ParamType):
    pass
            
class Param(object):
    """
    A required parameter for a method. Params can be passed to the method
    as an argument or a keyword.
    """
    def __init__(self, name, type=None, help=None):
        self.name = name
        self.help = help
        
    def validate(self, value):
        return True
        
    def __repr__(self):
        return '<Param "%s">' % self.name
    
class KeywordParam(Param):
    """
    An optional params for a method. KeywordParams must be passed as a
    keyword to a method.
    """
    def __init__(self, name, type=None, default=None, help=None):
        self.name = name
        self.default = default
        self.help = help
        
class Method(object):
    """
    Returns a Request object to call a SmugMug API method.
    """
    name = None
    useSsl = False
    responseKey = None
    params = ()
    keywordParams = ()
    
    def __init__(self):
        self._paramNames = [p.name.lower() for p in self.params]
        self._paramNames += [p.name.lower() for p in self.keywordParams]
            
    def on_success(self, smuggler, response):
        return response

    def map_params(self, inputParams):
        # Make all keys lowercase to map parameters without case
        inputParams = dict([(k.lower(), v) for k, v in inputParams.iteritems()])
        
        # Raise error on unkown inputParams
        for k in inputParams.keys():
            if k not in self._paramNames:
                raise NameError('Unknown parameter "%s" for method "%s".' % (k, self.name))
            
        params = {}
        for param in self.params:
            key = param.name.lower()
            if inputParams.has_key(key):
                param.validate(inputParams[key])
                params[param.name] = inputParams[key]
            else:
                raise ValueError('Parameter "%s" is required for method "%s".' % (param.name, self.name))

        for param in self.keywordParams:
            key = param.name.lower()
            if inputParams.has_key(key):
                param.validate(inputParams[key])
                params[param.name] = inputParams[key]
            elif param.default is not None:
                params[param.name] = param.default

        return params
        
    def __call__(self, smuggler, *args, **kwargs):
        if len(args) > len(self.params):
            raise Exception('Expected %d parameters and got %d.' % (len(self.params), len(args)))
            
        # Map arguements to their parameter names
        argnames = [i.name for i in self.params]
        kwargs.update(izip(argnames, args))
        params = self.map_params(kwargs)
        
        return self._get_request(smuggler, params)
        
    def _get_request(self, smuggler, params):
        if len(params) > len(self.params):
            # It looks like we have more than the required params, lets do this as a POST            
            return Request(self.name, data=params, useSsl=self.useSsl, httpMethod='POST')
        return Request(self.name, params=params, useSsl=self.useSsl)
        
class Response(object):
    def __init__(self, headers, content):
        self.stat = None
        self.method = None
        self.content = None

        if headers['status'] == '200' and content:
            # SmugMug doesn't send the correct content-type for JSON so we have to assume we were sent it anyway
            if headers['content-type'] == 'application/json':
                content = simplejson.loads(content)
            else:
                raise Exception('Did not get expected content type "application/json", got "%s" instead.' % headers['content-type'])

            self.stat = content['stat']
            self.method = content.get('method', None)
            self.content = content    

        self.headers = headers

    def __getattr__(self, key):
        return self.content[key]

class Request(object):
    def __init__(self, method, params=None, data=None, headers=None, useSsl=False, body=None, httpMethod='GET', uri=None):
        self.httpMethod = httpMethod
        self.method = method
        self.params = params
        self.data = data
        self.headers = headers
        self.useSsl = useSsl
        self.body = body
        self.uri = uri

class MethodRunner(object):
    def __init__(self, name, smuggler, module):
        self.smuggler = smuggler
        self.module = module
        self.name = name

    def __getattr__(self, key):       
        return MethodRunner(key, self.smuggler, getattr(self.module, key))

    def __call__(self, *args, **kwargs):
        if callable(self.module):
            return self.smuggler.execute(self.module, *args, **kwargs)
        elif callable(getattr(self.module, self.name)):
            # This is a module that defines a method with the same name, such as logout.logout()
            return self.smuggler.execute(getattr(self.module, self.name), *args, **kwargs)
        else:
            raise Exception('"%s" is not a callable method.' % self.name)